return result;
};
+ /**
+ * Get an array of currently applied highlight colors
+ *
+ * @return {string[]} Currently applied highlight colors
+ */
+ mw.rcfilters.dm.FiltersViewModel.prototype.getCurrentlyUsedHighlightColors = function () {
+ var result = [];
+
+ this.getHighlightedItems().forEach( function ( filterItem ) {
+ var color = filterItem.getHighlightColor();
+
+ if ( result.indexOf( color ) === -1 ) {
+ result.push( color );
+ }
+ } );
+
+ return result;
+ };
+
/**
* Sanitize value group of a string_option groups type
* Remove duplicates and make sure to only use valid
this.filtersModel.toggleInvertedNamespaces( !!Number( parameters.invert ) );
// Update highlight state
- this.filtersModel.toggleHighlight( !!Number( parameters.highlight ) );
this.filtersModel.getItems().forEach( function ( filterItem ) {
var color = parameters[ filterItem.getName() + '_color' ];
if ( color ) {
filterItem.clearHighlightColor();
}
} );
+ this.filtersModel.toggleHighlight( !!Number( parameters.highlight ) );
// Check all filter interactions
this.filtersModel.reassessFilterInteractions();
}
}
+// A mixin just for changesListWrapperWidget page, to output the scope of the widget
+// so it is before the rest of the rule; we need the li& to be in
+// between the wrapper scope and the color-cX class, which doesn't
+// work if the rules are inside the above widget LESS scope
+.highlight-changesListWrapperWidget( @bgcolor ) {
+ .mw-rcfilters-ui-changesListWrapperWidget li&,
+ .mw-rcfilters-ui-changesListWrapperWidget & tr:first-child,
+ .mw-rcfilters-ui-changesListWrapperWidget tr&.mw-rcfilters-ui-changesListWrapperWidget-enhanced-toplevel:not(.mw-rcfilters-ui-changesListWrapperWidget-enhanced-grey) td:not( :nth-child( -n+2 ) ),
+ .mw-rcfilters-ui-changesListWrapperWidget tr&.mw-rcfilters-ui-changesListWrapperWidget-enhanced-nested:not(.mw-rcfilters-ui-changesListWrapperWidget-enhanced-grey) td:not( :nth-child( -n+3 ) ) {
+ background-color: @bgcolor;
+ }
+}
+
// This mixin produces color mixes for two, three and four colors
.highlight-color-mix( @color1, @color2, @color3: false, @color4: false ) {
@highlight-color-class-var: ~'.mw-rcfilters-highlight-color-@{color1}.mw-rcfilters-highlight-color-@{color2}';
// Two colors
@{highlight-color-class-var} when ( @color3 = false ) and ( @color4 = false ) and not ( @color1 = false ), ( @color2 = false ) {
- background-color: tint( average( @@c1var, @@c2var ), 50% );
+ .highlight-changesListWrapperWidget( tint( average( @@c1var, @@c2var ), 50% ) );
}
// Three colors
@{highlight-color-class-var}.mw-rcfilters-highlight-color-@{color3} when ( @color4 = false ) and not ( @color3 = false ) {
@c3var: ~'highlight-@{color3}';
- background-color: tint( mix( @@c1var, average( @@c2var, @@c3var ), 33% ), 30% );
+ .highlight-changesListWrapperWidget( tint( mix( @@c1var, average( @@c2var, @@c3var ), 33% ), 30% ) );
}
// Four colors
@{highlight-color-class-var}.mw-rcfilters-highlight-color-@{color3}.mw-rcfilters-highlight-color-@{color4} when not ( @color4 = false ) {
@c3var: ~'highlight-@{color3}';
@c4var: ~'highlight-@{color4}';
- background-color: tint( mix( @@c1var, mix( @@c2var, average( @@c3var, @@c4var ), 25% ), 25% ), 25% );
+ .highlight-changesListWrapperWidget( tint( mix( @@c1var, mix( @@c2var, average( @@c3var, @@c4var ), 25% ), 25% ), 25% ) );
}
}
}
}
+ // Rule needs to be specific
+ // We want the expand button to appear outside the color
+ // to match the way the general highlight background appears
+ &-enhanced-grey td:not( :nth-child( -n+2 ) ) {
+ background-color: #dee0e3;
+ }
+
h4:first-of-type {
margin-top: 0;
padding-top: 0;
// and then plus the general margin
width: ~'calc( ( @{result-circle-diameter} + @{result-circle-margin} ) * 5 )';
// And we want to shift the entire block to the left of the li
- position: absolute;
- left: 0;
+ position: relative;
+ // Negative left margin of width + padding
+ margin-left: ~'calc( ( @{result-circle-diameter} + @{result-circle-margin} ) * -5 - @{result-circle-general-margin} )';
.mw-rcfilters-ui-changesListWrapperWidget-highlighted & {
display: inline-block;
.result-circle( c5 );
}
}
+}
- // One color
- .mw-rcfilters-highlight-color-c1 {
- background-color: tint( @highlight-c1, 70% );
- }
+// One color
+.mw-rcfilters-highlight-color-c1 {
+ .highlight-changesListWrapperWidget( tint( @highlight-c1, 70% ); );
+}
- .mw-rcfilters-highlight-color-c2 {
- background-color: tint( @highlight-c2, 70% );
- }
+.mw-rcfilters-highlight-color-c2 {
+ .highlight-changesListWrapperWidget( tint( @highlight-c2, 70% ); );
+}
- .mw-rcfilters-highlight-color-c3 {
- background-color: tint( @highlight-c3, 70% );
- }
+.mw-rcfilters-highlight-color-c3 {
+ .highlight-changesListWrapperWidget( tint( @highlight-c3, 70% ); );
+}
- .mw-rcfilters-highlight-color-c4 {
- background-color: tint( @highlight-c4, 70% );
- }
+.mw-rcfilters-highlight-color-c4 {
+ .highlight-changesListWrapperWidget( tint( @highlight-c4, 70% ); );
+}
- .mw-rcfilters-highlight-color-c5 {
- background-color: tint( @highlight-c5, 70% );
- }
+.mw-rcfilters-highlight-color-c5 {
+ .highlight-changesListWrapperWidget( tint( @highlight-c5, 70% ); );
+}
- // Two colors
- .highlight-color-mix( c1, c2 );
- // Overriding .highlight-color-mix( c1, c3 ); to produce
- // a custom color rather than the computed tint
- // see https://phabricator.wikimedia.org/T161267
- .mw-rcfilters-highlight-color-c1.mw-rcfilters-highlight-color-c3 {
- background-color: #ccdecc;
- }
- .highlight-color-mix( c1, c4 );
- .highlight-color-mix( c1, c5 );
- .highlight-color-mix( c2, c3 );
- .highlight-color-mix( c2, c4 );
- .highlight-color-mix( c2, c5 );
- .highlight-color-mix( c3, c4 );
- .highlight-color-mix( c3, c5 );
- .highlight-color-mix( c4, c5 );
-
- // Three colors
- .highlight-color-mix( c1, c2, c3 );
- .highlight-color-mix( c1, c2, c5 );
- .highlight-color-mix( c1, c2, c4 );
- .highlight-color-mix( c1, c3, c4 );
- .highlight-color-mix( c1, c3, c5 );
- .highlight-color-mix( c1, c4, c5 );
- .highlight-color-mix( c2, c3, c4 );
- .highlight-color-mix( c2, c3, c5 );
- .highlight-color-mix( c2, c4, c5 );
- .highlight-color-mix( c3, c4, c5 );
-
- // Four colors
- .highlight-color-mix( c1, c2, c3, c4 );
- .highlight-color-mix( c1, c2, c3, c5 );
- .highlight-color-mix( c1, c2, c4, c5 );
- .highlight-color-mix( c1, c3, c4, c5 );
- .highlight-color-mix( c2, c3, c4, c5 );
-
- // Five colors:
- .mw-rcfilters-highlight-color-c1.mw-rcfilters-highlight-color-c2.mw-rcfilters-highlight-color-c3.mw-rcfilters-highlight-color-c4.mw-rcfilters-highlight-color-c5 {
- background-color: tint( mix( @highlight-c1, mix( @highlight-c2, mix( @highlight-c3, average( @highlight-c4, @highlight-c5 ), 20% ), 20% ), 20% ), 15% );
- }
+// Two colors
+.highlight-color-mix( c1, c2 );
+// Overriding .highlight-color-mix( c1, c3 ); to produce
+// a custom color rather than the computed tint
+// see https://phabricator.wikimedia.org/T161267
+.mw-rcfilters-highlight-color-c1.mw-rcfilters-highlight-color-c3 {
+ .highlight-changesListWrapperWidget( #ccdecc );
+}
+.highlight-color-mix( c1, c4 );
+.highlight-color-mix( c1, c5 );
+.highlight-color-mix( c2, c3 );
+.highlight-color-mix( c2, c4 );
+.highlight-color-mix( c2, c5 );
+.highlight-color-mix( c3, c4 );
+.highlight-color-mix( c3, c5 );
+.highlight-color-mix( c4, c5 );
+
+// Three colors
+.highlight-color-mix( c1, c2, c3 );
+.highlight-color-mix( c1, c2, c5 );
+.highlight-color-mix( c1, c2, c4 );
+.highlight-color-mix( c1, c3, c4 );
+.highlight-color-mix( c1, c3, c5 );
+.highlight-color-mix( c1, c4, c5 );
+.highlight-color-mix( c2, c3, c4 );
+.highlight-color-mix( c2, c3, c5 );
+.highlight-color-mix( c2, c4, c5 );
+.highlight-color-mix( c3, c4, c5 );
+
+// Four colors
+.highlight-color-mix( c1, c2, c3, c4 );
+.highlight-color-mix( c1, c2, c3, c5 );
+.highlight-color-mix( c1, c2, c4, c5 );
+.highlight-color-mix( c1, c3, c4, c5 );
+.highlight-color-mix( c2, c3, c4, c5 );
+
+// Five colors:
+.mw-rcfilters-highlight-color-c1.mw-rcfilters-highlight-color-c2.mw-rcfilters-highlight-color-c3.mw-rcfilters-highlight-color-c4.mw-rcfilters-highlight-color-c5 {
+ .highlight-changesListWrapperWidget( tint( mix( @highlight-c1, mix( @highlight-c2, mix( @highlight-c3, average( @highlight-c4, @highlight-c5 ), 20% ), 20% ), 20% ), 15% ) );
}
this.filtersViewModel = filtersViewModel;
this.changesListViewModel = changesListViewModel;
this.controller = controller;
+ this.highlightClasses = null;
+ this.filtersModelInitialized = false;
// Events
this.filtersViewModel.connect( this, {
itemUpdate: 'onItemUpdate',
- highlightChange: 'onHighlightChange'
+ highlightChange: 'onHighlightChange',
+ initialize: 'onFiltersModelInitialize'
} );
this.changesListViewModel.connect( this, {
invalidate: 'onModelInvalidate',
// We handle our own display/hide of the empty results message
.removeClass( 'mw-changeslist-empty' );
- // Set up highlight containers
- this.setupHighlightContainers( this.$element );
-
this.setupNewChangesButtonContainer( this.$element );
};
OO.inheritClass( mw.rcfilters.ui.ChangesListWrapperWidget, OO.ui.Widget );
+ /**
+ * Respond to filters model initialize event
+ */
+ mw.rcfilters.ui.ChangesListWrapperWidget.prototype.onFiltersModelInitialize = function () {
+ this.filtersModelInitialized = true;
+ // Set up highlight containers. We need to wait for the filters model
+ // to be initialized, so we can make sure we have all the css class definitions
+ // we get from the server with our filters
+ this.setupHighlightContainers( this.$element );
+ };
+
+ /**
+ * Get all available highlight classes
+ *
+ * @return {string[]} An array of available highlight class names
+ */
+ mw.rcfilters.ui.ChangesListWrapperWidget.prototype.getHighlightClasses = function () {
+ if ( !this.highlightClasses || !this.highlightClasses.length ) {
+ this.highlightClasses = this.filtersViewModel.getItemsSupportingHighlights()
+ .map( function ( filterItem ) {
+ return filterItem.getCssClass();
+ } );
+ }
+
+ return this.highlightClasses;
+ };
+
/**
* Respond to the highlight feature being toggled on and off
*
* Respond to a filter item model update
*/
mw.rcfilters.ui.ChangesListWrapperWidget.prototype.onItemUpdate = function () {
- if ( this.filtersViewModel.isHighlightEnabled() ) {
+ if ( this.filtersModelInitialized && this.filtersViewModel.isHighlightEnabled() ) {
this.clearHighlight();
this.applyHighlight();
}
* @param {jQuery|string} $content The content of the updated changes list
*/
mw.rcfilters.ui.ChangesListWrapperWidget.prototype.setupHighlightContainers = function ( $content ) {
- var highlightClass = 'mw-rcfilters-ui-changesListWrapperWidget-highlights',
+ var $enhancedTopPageCell, $enhancedNestedPagesCell,
+ widget = this,
+ highlightClass = 'mw-rcfilters-ui-changesListWrapperWidget-highlights',
$highlights = $( '<div>' )
.addClass( highlightClass )
.append(
} );
if ( this.inEnhancedMode() ) {
- // Enhanced RC
- $content.find( 'td.mw-enhanced-rc' )
- .parent()
+ $enhancedTopPageCell = $content.find( 'table.mw-enhanced-rc.mw-collapsible' );
+ $enhancedNestedPagesCell = $content.find( 'td.mw-enhanced-rc-nested' );
+
+ // Enhanced RC highlight containers
+ $content.find( 'table.mw-enhanced-rc tr:first-child' )
+ .addClass( 'mw-rcfilters-ui-changesListWrapperWidget-enhanced-toplevel' )
.prepend(
$( '<td>' )
.append( $highlights.clone() )
);
+
+ // We are adding and changing cells in a table that, despite having nested rows,
+ // is actually all one big table. To do that right, we want to remove the 'placeholder'
+ // cell from the top row, because we're actually adding that placeholder in the children
+ // with the highlights.
+ $content.find( 'table.mw-enhanced-rc tr:first-child td.mw-changeslist-line-prefix' )
+ .detach();
+ $content.find( 'table.mw-enhanced-rc tr:first-child td.mw-enhanced-rc' )
+ .prop( 'colspan', '2' );
+
+ $enhancedNestedPagesCell
+ .before(
+ $( '<td>' )
+ .append( $highlights.clone().addClass( 'mw-enhanced-rc-nested' ) )
+ );
+
+ // We need to target the nested rows differently than the top rows so that the
+ // LESS rules applies correctly. In top rows, the rule should highlight all but
+ // the first 2 cells td:not( :nth-child( -n+2 ) and the nested rows, the rule
+ // should highlight all but the first 3 cells td:not( :nth-child( -n+3 )
+ $enhancedNestedPagesCell
+ .closest( 'tr' )
+ .addClass( 'mw-rcfilters-ui-changesListWrapperWidget-enhanced-nested' );
+
+ // Go over pages that have sub results
+ // HACK: We really only can collect those by targetting the collapsible class
+ $enhancedTopPageCell.each( function () {
+ var collectedClasses,
+ $table = $( this );
+
+ // Go over <tr>s and pick up all recognized classes
+ collectedClasses = widget.getHighlightClasses().filter( function ( className ) {
+ return $table.find( 'tr' ).hasClass( className );
+ } );
+
+ $table.find( 'tr:first-child' )
+ .addClass( collectedClasses.join( ' ' ) );
+ } );
+
+ $content.addClass( 'mw-rcfilters-ui-changesListWrapperWidget-enhancedView' );
} else {
// Regular RC
$content.find( 'ul.special li' )
}
};
+ /**
+ * In enhanced mode, we need to check whether the grouped results all have the
+ * same active highlights in order to see whether the "parent" of the group should
+ * be grey or highlighted normally.
+ *
+ * This is called every time highlights are applied.
+ */
+ mw.rcfilters.ui.ChangesListWrapperWidget.prototype.updateEnhancedParentHighlight = function () {
+ var activeHighlightClasses,
+ $enhancedTopPageCell = this.$element.find( 'table.mw-enhanced-rc.mw-collapsible' );
+
+ activeHighlightClasses = this.filtersViewModel.getCurrentlyUsedHighlightColors().map( function ( color ) {
+ return 'mw-rcfilters-highlight-color-' + color;
+ } );
+
+ // Go over top pages and their children, and figure out if all sub-pages have the
+ // same highlights between themselves. If they do, the parent should be highlighted
+ // with all colors. If classes are different, the parent should receive a grey
+ // background
+ $enhancedTopPageCell.each( function () {
+ var firstChildClasses, $rowsWithDifferentHighlights,
+ $table = $( this );
+
+ // Collect the relevant classes from the first nested child
+ firstChildClasses = activeHighlightClasses.filter( function ( className ) {
+ return $table.find( 'tr:nth-child(2)' ).hasClass( className );
+ } );
+ // Filter the non-head rows and see if they all have the same classes
+ // to the first row
+ $rowsWithDifferentHighlights = $table.find( 'tr:not(:first-child)' ).filter( function () {
+ var classesInThisRow,
+ $this = $( this );
+
+ classesInThisRow = activeHighlightClasses.filter( function ( className ) {
+ return $this.hasClass( className );
+ } );
+
+ return !OO.compare( firstChildClasses, classesInThisRow );
+ } );
+
+ // If classes are different, tag the row for using grey color
+ $table.find( 'tr:first-child' )
+ .toggleClass( 'mw-rcfilters-ui-changesListWrapperWidget-enhanced-grey', $rowsWithDifferentHighlights.length > 0 );
+ } );
+ };
+
/**
* @return {boolean} Whether the changes are grouped by page
*/
}
} );
+ if ( this.inEnhancedMode() ) {
+ this.updateEnhancedParentHighlight();
+ }
+
// Turn on highlights
this.$element.addClass( 'mw-rcfilters-ui-changesListWrapperWidget-highlighted' );
};
this.$element.find( '[data-highlightedFilters]' )
.removeAttr( 'title' )
.removeAttr( 'data-highlightedFilters' );
+
+ // Remove grey from enhanced rows
+ this.$element.find( '.mw-rcfilters-ui-changesListWrapperWidget-enhanced-grey' )
+ .removeClass( 'mw-rcfilters-ui-changesListWrapperWidget-enhanced-grey' );
+
// Turn off highlights
this.$element.removeClass( 'mw-rcfilters-ui-changesListWrapperWidget-highlighted' );
};